package graph.editor;

import graph.util.Shapes;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RectangularShape;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.event.MouseInputListener;

import observable.ObservableAutomaton;

import automaton.DeterministicAutomaton;
import automaton.State;
import automaton.StateImpl;
import automaton.Transition;
import automaton.TransitionImpl;

public class GraphComponent extends JComponent implements MouseInputListener,
		KeyListener {

	/**
	 * 
	 */
	//Transition<String>[] transitions;
	
	private static final long serialVersionUID = 1L;

	private static int n = 0;

	List<Vertex> vertices = new ArrayList<Vertex>();
	Vertex currentVertex = null;

	private RectangularShape vertexSample = new Ellipse2D.Double(0, 0, 10, 10);

	private RectangularShape currentJointPoint = null;

	List<Edge> edges = new ArrayList<Edge>();
	Edge currentEdge = null;

	public GraphComponent() {
		addMouseListener(this);
		addMouseMotionListener(this);
		addKeyListener(this);
		
	}

	protected void paintComponent(Graphics g) {
		if (isOpaque()) {
			g.setColor(getBackground());
			g.fillRect(0, 0, getWidth(), getHeight());
		}
		g.setColor(getForeground());
		Graphics2D g2 = (Graphics2D) g;
		for (Vertex v : vertices)
			v.draw(g2);
		for (Edge e : edges)
			e.draw(g2);
	}

	private Vertex getVertex(int x, int y) {
		for (Vertex v : vertices) {
			if (v.shape.contains(x, y))
				return v;
		}
		return null;
	}

	private RectangularShape getJointPoint(int x, int y) {
		for (Edge e : edges) {
			RectangularShape jp = e.getJointPoint(x, y);
			if (jp != null)
				return jp;
		}
		return null;
	}

	private static final double EDGE_EPSILON = 2.0;

	private Edge getEdge(int x, int y) {
		for (Edge e : edges)
			if (e.contains(x, y, EDGE_EPSILON))
				return e;
		return null;
	}

	public void setVertexSample(RectangularShape sample) {
		vertexSample = sample;
	}

	private RectangularShape newVertexShape(int x, int y) {
		RectangularShape rs = (RectangularShape) vertexSample.clone();
		Shapes.moveShape(rs, x, y);
		return rs;
	}

	private Vertex createVertex(int x, int y) {
		RectangularShape rs = newVertexShape(x, y);
		Vertex v = new Vertex(rs, Integer.toString(n++));
		vertices.add(v);		
		return v;
	}

	private void removeVertex(Vertex v) {
		List<Edge> toRemove = new ArrayList<Edge>();
		for (Edge e : edges) {
			if (e.v1 == v || e.v2 == v)
				toRemove.add(e);
		}
		for (Edge e : toRemove)
			removeEdge(e);
		vertices.remove(v);
				
		// Đánh số lại cho vertices
		this.n = this.n - 1;
		int count=0;
		for(Vertex _v : vertices){
			_v.label = Integer.toString(count);
			count++;
		}
		
		// Vẽ lại
		repaint();
		
	}

	private void removeEdge(Edge e) {
		remove(e.textField);
		edges.remove(e);
	}

	private Edge startEdge(Vertex v) {
		RectangularShape rs2 = newVertexShape(0, 0);
		RectangularShape rs = v.shape;
		rs2.setFrameFromCenter((int) rs.getCenterX(), (int) rs.getCenterY(),
				(int) rs.getCenterX(), (int) rs.getCenterY());
		Edge l = new Edge(v, new Vertex(rs2, null));
		edges.add(l);
		return l;
	}

	private void addJointPoint(Edge e) {
		e.addJointPoint();
	}

	private void endLine(Edge e, int x, int y) {
		Vertex v = getVertex(x, y);
		if (v == null) {
			e.v2.shape.setFrameFromCenter(x, y, x + vertexSample.getHeight()
					/ 2, y + vertexSample.getWidth() / 2);
			e.v2.label = Integer.toString(n++);
			vertices.add(e.v2);
		} else
			e.v2 = v;
	}

	public void mousePressed(MouseEvent e) {
		requestFocusInWindow();
		if ((e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) == InputEvent.BUTTON3_DOWN_MASK)
			return;
		int x = e.getX();
		int y = e.getY();
		Vertex v = getVertex(x, y);
		if (v == null) {
			currentJointPoint = getJointPoint(x, y);
		}
		if (v == null && currentJointPoint == null)
			v = createVertex(x, y);
		if (v != null && e.isAltDown())
			currentEdge = startEdge(v);
		else
			currentVertex = v;
		repaint();
	}

	public void mouseDragged(MouseEvent e) {
		if (currentVertex != null) {
			Shapes.moveShape(currentVertex.shape, e.getX(), e.getY());
			repaint();
		} else if (currentEdge != null) {
			Shapes.moveShape(currentEdge.v2.shape, e.getX(), e.getY());
			repaint();
		} else if (currentJointPoint != null) {
			Shapes.moveShape(currentJointPoint, e.getX(), e.getY());
			repaint();
		}
	}

	public void addEdgeLabel(final Edge e) {
		final JTextField textField = new JTextField();
		textField.setSize(100, 20);
		textField.setLocation(e.labelPosition());
		e.textField = textField;
		this.add(textField);
	}

	public void mouseReleased(MouseEvent e) {
		if (currentEdge != null) {
			endLine(currentEdge, e.getX(), e.getY());
			addEdgeLabel(currentEdge);
			currentEdge = null;
			repaint();
		}
		currentVertex = null;
		currentJointPoint = null;
	}

	public void mouseMoved(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mouseClicked(MouseEvent e) {
		if (e.getButton() == MouseEvent.BUTTON3) {
			int x = e.getX();
			int y = e.getY();
			Vertex v = getVertex(x, y);
			if (v != null) {
				removeVertex(v);
				repaint();
				return;
			}
			for (Edge edge : edges) {
				RectangularShape jp = edge.getJointPoint(x, y);
				if (jp != null) {
					edge.removeJointPoint(jp);
					repaint();
					return;
				}
			}
			Edge edge = getEdge(x, y);
			if (edge != null) {
				removeEdge(edge);
				repaint();
				return;
			}
		}
	}

	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == ' ' && currentEdge != null) {
			addJointPoint(currentEdge);
		}
	}

	public void keyReleased(KeyEvent e) {
	}

	public void keyTyped(KeyEvent e) {
	}
	
	/*
	 Check whether state is set final state
	 */
	
	private boolean CheckIsFinalState(int state , String[] finalState)
	{
		for(String s : finalState){
			if(state == Integer.parseInt(s)){
				return true;
			}
		}
		return false;
	}
	
	/*
	 * Get state which has label
	 */
	private State GetStateWithLabel(State[] state , String label){
		for(State st : state){
			if(st.GetLabel().compareTo(label) == 0){
				return st;
			}
		}
		return null;
	}
	
	/*
	This funtion will convert graph to automata and run your application
	*/
	public void convertGraphIntoAutomaton(String initalState , String finalState , String testString )

	{
		int _initialState = Integer.parseInt(initalState);
		String []_finalState = finalState.split(",");
		String []_testString = testString.split(",");
		
		final State[] states = new State[vertices.size()];
		int istate = 0;
		for(Vertex ver : vertices){	
				if(istate == _initialState){
					if(CheckIsFinalState(istate, _finalState)){
						states[istate] = new StateImpl(true, true);
					}
					else{
						states[istate] = new StateImpl(true, false);
					}
				}
				else{
					if(CheckIsFinalState(istate, _finalState)){
						states[istate] = new StateImpl(false, true);
					}
					else{
						states[istate] = new StateImpl(false, false);
					}
				}
				states[istate].SetLabel(ver.label);
				istate++;
		}
		
		Transition<String>[] transitions = new Transition[edges.size()];
		
		int iedg = 0;
		for (Edge ed : edges){
			//int indexv1 = Integer.parseInt(ed.v1.label);
			//int indexv2 = Integer.parseInt(ed.v2.label);
			String txtTrans = ed.textField.getText();
			transitions[iedg] = new TransitionImpl<String>(GetStateWithLabel(states , ed.v1.label), GetStateWithLabel(states,ed.v2.label), txtTrans );
			iedg++;
		}
		
		try {
			
			DeterministicAutomaton<String> a = new DeterministicAutomaton<String>(
					transitions);
			
			if(a.recognize(_testString))
			{
				// draw
				try {
					ObservableAutomaton<String> observable = new ObservableAutomaton<String>(
							transitions);
					observable.addObserver(new Observer() {
						public void update(Observable arg0, Object arg1) {
							//System.out.print(((Transition<String>) arg1).label());
							
							// Get Index Vertex Source của ((Transition<String>) arg1)
							int indexStateSource = 0;
							for(State ste : states){
								if(ste.equals(((Transition<String>) arg1).source())){
									break;
								}
								indexStateSource++;
							}
							
							// Get Vertex Source của ((Transition<String>) arg1)
							Vertex src_ver = null;
							for(Vertex _v1 : vertices){
								String indexStateSrc = Integer.toString(indexStateSource);
								
								if(_v1.label.compareTo(indexStateSrc) == 0){
									src_ver = _v1;
									break;
								}
							}
							
							// Get Index Vertex Target của ((Transition<String>) arg1)
							int indexStateTarget = 0;
							for(State ste : states){
								if(ste.equals(((Transition<String>) arg1).target())){
									break;
								}
								indexStateTarget++;
							}
							
							// Get Vertex Target của ((Transition<String>) arg1)
							Vertex tar_ver = null;
							for(Vertex _v1 : vertices){
								
								String indexStateTar =Integer.toString(indexStateTarget);
								
								if(_v1.label.compareTo(indexStateTar) == 0){
									tar_ver = _v1;
									break;
								}
							}
							
							
							// Get Edge của ((Transition<String>) arg1)
							Edge _edge = null;
							for(Edge e: edges){
								if(e.textField.getText().compareTo(((Transition<String>) arg1).label())==0 && e.v1.equals(src_ver) && e.v2.equals(tar_ver)){
									_edge = e;
									break;
								}
							}
							
							processVisualization(src_ver , tar_ver ,  _edge);
						}
					});
					
					observable.recognize(_testString);
					System.out.println();
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				
				System.out.println("m1 recognized ? true");
			}
			else{
				System.out.println("m1 recognized ? false");
			}
			

		} catch (Exception e) {
			System.out.println("Error: "+ e.getMessage());
			
		}
	}
	
	
	/*
	 * This method will cause a delay of i microsecond.
	 */
	private void delay(int i) {
		try {
			Thread.sleep(i);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/*
	 * This method is used to visualize the state change on the component.
	 * it will do as follows:
	 * 		- draw the source state and delay an amount of time
	 * 		- draw the edge connecting the source state and the target state and delay an amount of time
	 * 		- draw the target state and delay an amount of time
	 */
	private void processVisualization(Vertex _v1 , Vertex _v2 , Edge _ed) {
		if (_v1 == null | _v2 == null | _ed == null) {
			System.out.println("Error");
			return;
		}
		Graphics2D g2 = (Graphics2D) getGraphics();
		
		g2.setColor(Color.RED);
		delay(1000);
		_v1.draw(g2);
		g2.setColor(Color.BLUE);
		delay(1000);
		_ed.draw(g2);
		delay(1000);
		g2.setColor(Color.RED);
		_v2.draw(g2);
		delay(1000);
		delay(500);
		repaint();
		
		g2.setColor(getForeground());
	}
	
	/*
	 * Save object into hard disk with path
	 */
	public void Save(String path){
		GraphObject grObj = new GraphObject();
		
		grObj._n = this.n;
		
		grObj.vertices = this.vertices;
		grObj.currentVertex = this.currentVertex;
		grObj.vertexSample = this.vertexSample;
		grObj.currentJointPoint = this.currentJointPoint;
		
		grObj.edges = this.edges;
		grObj.currentEdge = this.currentEdge;
		
		
		try {
			SerialObject(path, grObj);
			System.out.println("Save successful!");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("Save fail! "+ e.getMessage());
		}
		
	}
	
	/*
	 * Open file and re-draw graph
	 */
	public void Open(String path){
		GraphObject grObj = new GraphObject();
		try {
			grObj = (GraphObject) DeserialObject(path);
			
			// Draw
			
			this.n = grObj._n;
			
			this.vertices = grObj.vertices;
			this.currentVertex = grObj.currentVertex;
			this.vertexSample = grObj.vertexSample;
			this.currentJointPoint = grObj.currentJointPoint;
			
			this.edges = grObj.edges;
			this.currentEdge = grObj.currentEdge;
			
			repaint();
			
			
			for(Edge e: edges){
				this.add(e.textField);
			}
				
			System.out.println("Open successful!");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("Open fail! "+ e.getMessage());
		}
		
		
	}
	
	/**
	* Serial 1 đối tượng xuống file
	* @param fileName file chỉ định
	* @param h: đối tượng cần serial
	* @throws Exception
	*/
	private void SerialObject(String fileName, Object obj) throws Exception{
		//Tạo luồng ghi file
		FileOutputStream fs=new FileOutputStream(fileName);
		//Tạo luồng để serial đối tượng
		ObjectOutputStream os=new ObjectOutputStream(fs);
		//chuyển tải đối tượng tới đích (tập tin)
		os.writeObject(obj);
		//đóng luồng
		fs.close();os.close();
		}
	
	/**
	* Khôi phục(deserial) 1 đối tượng đã được serial trước đó
	* lên bộ nhớ.
	* @param fileName: file chỉ định
	* @return đối tượng đã được phục hồi
	* @throws Exception
	*/
	private Object DeserialObject(String fileName) throws Exception{
	Object kp=null;
	//Tạo luồng đọc file đã được serial
	FileInputStream fi=new FileInputStream(fileName);
	//Tạo luồng để Deserialize đối tượng
	ObjectInputStream ois=new ObjectInputStream(fi);
	//Tiến hành khôi phục đối tượng
	kp=ois.readObject();
	//đóng luồng
	fi.close();ois.close();
	return kp;
	}

	/*
	 * Inner class, this class will implement Serializable to save current status of graph and automata
	 */
	class GraphObject implements java.io.Serializable{
		
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		int _n = 0;
		
		List<Vertex> vertices = new ArrayList<Vertex>();
		Vertex currentVertex = null;

		private RectangularShape vertexSample = new Ellipse2D.Double(0, 0, 10, 10);

		private RectangularShape currentJointPoint = null;

		List<Edge> edges = new ArrayList<Edge>();
		Edge currentEdge = null;
		
	}

}
